/* * Copyright 2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.spockframework.runtime; import org.junit.ComparisonFailure; import org.junit.internal.AssumptionViolatedException; import org.junit.runners.model.MultipleFailureException; import org.junit.runner.Description; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.spockframework.runtime.condition.IObjectRenderer; import org.spockframework.runtime.model.*; import org.spockframework.util.InternalSpockError; import org.spockframework.util.TextUtil; import static org.spockframework.runtime.RunStatus.*; public class JUnitSupervisor implements IRunSupervisor { private final SpecInfo spec; private final RunNotifier notifier; private final IStackTraceFilter filter; private final IRunListener masterListener; private final IObjectRenderer<Object> diffedObjectRenderer; private FeatureInfo currentFeature; private IterationInfo currentIteration; private int iterationCount; private boolean errorSinceLastReset; public JUnitSupervisor(SpecInfo spec, RunNotifier notifier, IStackTraceFilter filter, IObjectRenderer<Object> diffedObjectRenderer) { this.spec = spec; this.notifier = notifier; this.filter = filter; this.masterListener = new MasterRunListener(spec); this.diffedObjectRenderer = diffedObjectRenderer; } public void beforeSpec(SpecInfo spec) { masterListener.beforeSpec(spec); } public void beforeFeature(FeatureInfo feature) { masterListener.beforeFeature(feature); currentFeature = feature; if (!feature.isReportIterations()) notifier.fireTestStarted(feature.getDescription()); if (feature.isParameterized()) { iterationCount = 0; errorSinceLastReset = false; } } public void beforeIteration(IterationInfo iteration) { masterListener.beforeIteration(iteration); currentIteration = iteration; iterationCount++; if (currentFeature.isReportIterations()) notifier.fireTestStarted(iteration.getDescription()); } public int error(ErrorInfo error) { Throwable exception = error.getException(); if (exception instanceof MultipleFailureException) return handleMultipleFailures(error); if (isFailedEqualityComparison(exception)) exception = convertToComparisonFailure(exception); filter.filter(exception); Failure failure = new Failure(getCurrentDescription(), exception); if (exception instanceof AssumptionViolatedException) { // Spock has no concept of "violated assumption", so we don't notify Spock listeners // do notify JUnit listeners unless it's a data-driven iteration that's reported as one feature if (currentIteration == null || !currentFeature.isParameterized() || currentFeature.isReportIterations()) { notifier.fireTestAssumptionFailed(failure); } } else { masterListener.error(error); notifier.fireTestFailure(failure); } errorSinceLastReset = true; return statusFor(error); } // for better JUnit compatibility, e.g when a @Rule is used private int handleMultipleFailures(ErrorInfo error) { MultipleFailureException multiFailure = (MultipleFailureException) error.getException(); int runStatus = OK; for (Throwable failure : multiFailure.getFailures()) runStatus = error(new ErrorInfo(error.getMethod(), failure)); return runStatus; } private boolean isFailedEqualityComparison(Throwable exception) { if (!(exception instanceof ConditionNotSatisfiedError)) return false; ConditionNotSatisfiedError conditionNotSatisfiedError = (ConditionNotSatisfiedError) exception; Condition condition = conditionNotSatisfiedError.getCondition(); ExpressionInfo expr = condition.getExpression(); return expr != null && expr.isEqualityComparison() && // it is equality conditionNotSatisfiedError.getCause() == null; // and it is not failed because of exception } // enables IDE support (diff dialog) private Throwable convertToComparisonFailure(Throwable exception) { assert isFailedEqualityComparison(exception); ConditionNotSatisfiedError conditionNotSatisfiedError = (ConditionNotSatisfiedError) exception; Condition condition = conditionNotSatisfiedError.getCondition(); ExpressionInfo expr = condition.getExpression(); String actual = renderValue(expr.getChildren().get(0).getValue()); String expected = renderValue(expr.getChildren().get(1).getValue()); ComparisonFailure failure = new SpockComparisonFailure(condition, expected, actual); failure.setStackTrace(exception.getStackTrace()); if (conditionNotSatisfiedError.getCause()!=null){ failure.initCause(conditionNotSatisfiedError.getCause()); } return failure; } private String renderValue(Object value) { try { return diffedObjectRenderer.render(value); } catch (Throwable t) { return "Failed to render value due to:\n\n" + TextUtil.printStackTrace(t); } } private int statusFor(ErrorInfo error) { switch (error.getMethod().getKind()) { case DATA_PROCESSOR: case INITIALIZER: case ITERATION_EXECUTION: case SETUP: case CLEANUP: case FEATURE: return END_ITERATION; case FEATURE_EXECUTION: case DATA_PROVIDER: return END_FEATURE; case SHARED_INITIALIZER: case SETUP_SPEC: case CLEANUP_SPEC: case SPEC_EXECUTION: return END_SPEC; default: throw new InternalSpockError("unknown method kind"); } } public void afterIteration(IterationInfo iteration) { masterListener.afterIteration(iteration); if (currentFeature.isReportIterations()) notifier.fireTestFinished(iteration.getDescription()); currentIteration = null; } public void afterFeature(FeatureInfo feature) { if (feature.isParameterized()) { if (iterationCount == 0 && !errorSinceLastReset) notifier.fireTestFailure(new Failure(feature.getDescription(), new SpockExecutionException("Data provider has no data"))); } masterListener.afterFeature(feature); if (!feature.isReportIterations()) notifier.fireTestFinished(feature.getDescription()); currentFeature = null; } public void afterSpec(SpecInfo spec) { masterListener.afterSpec(spec); } public void specSkipped(SpecInfo spec) { masterListener.specSkipped(spec); notifier.fireTestIgnored(spec.getDescription()); } public void featureSkipped(FeatureInfo feature) { masterListener.featureSkipped(feature); notifier.fireTestIgnored(feature.getDescription()); } private Description getCurrentDescription() { if (currentIteration != null && currentFeature.isReportIterations()) return currentIteration.getDescription(); if (currentFeature != null) return currentFeature.getDescription(); return spec.getDescription(); } }